use super::output::Link;
use super::*;
use colored::*;
use const_format::formatcp;
// use std::borrow::Cow;
use std::io;
use std::process::{Command, Output};
pub mod adb;
pub mod cos;
pub mod imagemagick;
pub mod tesseract;

#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
pub enum Prequires {
  ADB,
  COS,
  PSEUDO,
  TESSERACT,
  IMAGEMAGICK,
}

// type std_output_formatter =  dyn FnOnce(String) -> String;

fn process_stdout<T: AsRef<str>, F>(
  output: io::Result<Output>,
  format_output: F,
  warning_check_strs: Option<&[T]>,
  cust_err: Option<&str>,
) where
  // F: for<'a> Fn(&'a str) -> Cow<'a, str>,
  //  对trait 的lifetime定义需要定义在where的for里,但是此种定义无法跨越编译器,因为无法得知input的&str和返回的&str哪个存活更久
  // 除非输入的lifetime是'static,明确outlive 输出‘a
  // 所以改成a)输入&str,输出String,b)输入String,输出String
  F: Fn(&str) -> String,
{
  match output {
    Ok(output) => {
      let mut print_output = true;
      if !output.status.success() {
        let stderr = String::from_utf8(output.stderr).unwrap();
        eprintln!("{}", stderr.red());
      } else {
        let output = String::from_utf8(output.stdout).unwrap();
        if let Some(warning_check_strs) = warning_check_strs {
          for warning_check_str in warning_check_strs {
            match output.find(warning_check_str.as_ref()) {
              Some(_) => {
                eprintln!("{}", output.yellow());
                return;
              }
              None => {
                print_output = print_output && true;
              }
            };
          }
        } else {
          print_output = true;
        }
        if print_output {
          let output = format_output(&output);
          if !output.is_empty() {
            println!("{}", output.green());
          }
        }
      }
    }
    Err(err) => {
      eprintln!(
        "command exec fail:\n{}.",
        cust_err.unwrap_or(&err.to_string()).red(),
      );
    }
  }
}

pub trait BinaryCommand {
  type Output;
  fn exec(&self) -> Self::Output;
  fn process_stdout<T: AsRef<str>>(
    &self,
    output: io::Result<Output>,
    warning_check_strs: Option<&[T]>,
    cust_err: Option<&str>,
  ) {
    process_stdout(
      output,
      |s: &str| s.to_string(),
      warning_check_strs,
      cust_err,
    );
  }
}

#[derive(Debug)]
pub struct PrequireCmd<'a> {
  name: &'a str,
  args: &'a str,
  help_link: Option<Link<'a>>,
  cust_err: Option<&'a str>,
}

impl<'a> PrequireCmd<'a> {
  pub const fn new(
    name: &'a str,
    args: &'a str,
    help_link: Option<Link<'a>>,
    cust_err: Option<&'a str>,
  ) -> Self {
    PrequireCmd {
      name,
      args,
      help_link,
      cust_err,
    }
  }
}

impl<'a> BinaryCommand for PrequireCmd<'a> {
  type Output = bool;
  fn exec(&self) -> Self::Output {
    if cfg!(target_os = "windows") {
      panic!("no impl for windows!")
    } else {
      let output = Command::new(self.name).arg(self.args).output();
      match output {
        Ok(output) => {
          return output.status.success();
        }
        Err(err) => {
          let help_link = self
            .help_link
            .as_ref()
            .map_or("none".to_owned(), |v| v.to_string());
          eprintln!(
            "prequire<{}> command exec fail:\n{}. \n\nSee:{}",
            self.name,
            self.cust_err.unwrap_or(&err.to_string()),
            help_link,
          );
          return false;
        }
      }
    }
  }
}

const PSEUDO_BIN: &str = "pseudo";

pub static CMD_CHECK_PSEUDO: PrequireCmd = PrequireCmd::new(
  PSEUDO_BIN,
  "--version",
  Some(Link::new("安装 pseudo", "https://pseudo.cn/12345678")),
  Some(formatcp!(
    "Not found {0}!!\nPlease install {0} first.",
    PSEUDO_BIN
  )),
);

pub fn check_prequires(prequire: &Prequires) -> bool {
  match prequire {
    Prequires::PSEUDO => prequire::CMD_CHECK_PSEUDO.exec(),
    Prequires::ADB => adb::CMD_CHECK_ADB.exec(),
    Prequires::COS => cos::CMD_CHECK_COS.exec(),
    Prequires::TESSERACT => tesseract::CMD_CHECK_TESSERACT.exec(),
    Prequires::IMAGEMAGICK => imagemagick::CMD_CHECK_IMAGEMAGICK.exec(),
    #[allow(unreachable_patterns)]
    _ => adb::CMD_CHECK_ADB.exec(),
  }
}
